#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>

#include "../mickey_bitslice.h"
#include "eSTREAM_MICKEY_2/ecrypt-sync.h"

#define NR_STREAMS 64 /* this should = your machine word-size */

#if NR_STREAMS==64
uint64_t genrand_int64(void) {
   uint64_t l,h;
   l=genrand_int32();
   h=genrand_int32();
   return ((h<<32)|l);
}
#define genrand_int genrand_int64
#else
#define genrand_int genrand_int32
#endif

static void seed_mt(void) {
   uint32_t seed;
   FILE *pf;

   if( !(pf=fopen("/dev/urandom", "r")) ) {
      fprintf(stderr,"Could not open /deve/urandom: %s\n",strerror(errno));
      seed=time(0);
   } else if (fread(&seed,sizeof(seed),1,pf)!=1) {
      fprintf(stderr,"Failed to read from /dev/urandom: %s\n",strerror(errno));
      seed=time(0);
      fclose(pf);
   }

   printf("seed=0x%08x;\n",seed);

   init_genrand(seed);
}

#define __mod_high(x,M)                 \
   ( ( (x)>=(M) ) ? ( (x)-(M) ) : (x) ) \

static int compare_Rs(mickey_state *bit_ctx, int stream_nr, ECRYPT_ctx *ctx) {
   int i,count=0;
   for(i=0;i<4;i++) {
      int j;
      u32 w=ctx->R[i];
      for(j=0;j<sizeof(w)*8;j++) {
         if( (w&1) != 
         ((bit_ctx->R[__mod_high(bit_ctx->R0_pos+count,100)]>>stream_nr)&1) ){
            fprintf(stderr,"Stream %2d, R[%2d] differ!\n",stream_nr,count);
            return -1;
         }
         w>>=1;
         count++;
         if(count>=100) break;
      }
   }
}

static int compare_Ss(mickey_state *bit_ctx, int stream_nr, ECRYPT_ctx *ctx) {
   int i,count=0;
   for(i=0;i<4;i++) {
      int j;
      u32 w=ctx->S[i];
      for(j=0;j<sizeof(w)*8;j++) {
         if( (w&1) != ((bit_ctx->S[count]>>stream_nr)&1) ){
            fprintf(stderr,"Stream %2d, S[%2d] differ!\n",stream_nr,count);
            return -1;
         }
         w>>=1;
         count++;
         if(count>=100) break;
      }
   }
}

static int test_iv(int nr_iter,int iv_len) {
   int i,nr;
   uword mixing,input_bits;
   u8 key[10];
   u8 iv[10];

   ECRYPT_ctx ctxs[MICKEY_NR_STREAMS];
   mickey_state ctx;

   /* Initialize to all-zeros */
   mickey_init(&ctx);
   for(i=0;i<MICKEY_NR_STREAMS;i++) {
      { 
         int k;
         for(k=0;k<sizeof(key);k++) { key[k]=genrand_int32()&0xff; }
      }
      ECRYPT_keysetup(&ctxs[i],key,80,iv_len);
      mickey_load_key(&ctx,key);
   }
   for(i=0;i<MICKEY_NR_STREAMS;i++) {
      { 
         int k;
         for(k=0;k<sizeof(iv);k++) { iv[k]=genrand_int32()&0xff; }
      }
      ECRYPT_ivsetup(&ctxs[i],iv);
      mickey_load_IV(&ctx,iv,iv_len);
   }
   mickey_setup(&ctx);

   for(nr=0;nr<nr_iter;nr++) {

      mixing=genrand_int();
      input_bits=genrand_int();

      /* Clock states */
      clock_kg(&ctx,mixing,input_bits);

      for(i=0;i<MICKEY_NR_STREAMS;i++) {
         CLOCK_KG(&ctxs[i],(mixing>>i)&1,(input_bits>>i)&1);
      }

      /* Compare states */
      for(i=0;i<MICKEY_NR_STREAMS;i++) {
         if(compare_Rs(&ctx, i, &ctxs[i])) {
            ECRYPT_print_R(&ctxs[i]);
            mickey_print_R(&ctx);
            fprintf(stderr,"Failed at iteration #=%d\n",nr);
            return -1;
         }
         if(compare_Ss(&ctx, i, &ctxs[i])) {
            ECRYPT_print_S(&ctxs[i]);
            mickey_print_S(&ctx);
            fprintf(stderr,"Failed at iteration #=%d\n",nr);
            return -1;
         }
      }

   }

   return 0;
}
static int test_keystream_bytes(int nr_iter,int iv_len) {
   int i,nr;
   u8 key[10];
   u8 iv[10];
   u8 keystream_bytes_bitslice[MICKEY_NR_STREAMS];
   u8 keystream_byte;

   ECRYPT_ctx ctxs[MICKEY_NR_STREAMS];
   mickey_state ctx;

   /* Initialize to all-zeros */
   mickey_init(&ctx);
   for(i=0;i<MICKEY_NR_STREAMS;i++) {
      { 
         int k;
         for(k=0;k<sizeof(key);k++) { key[k]=genrand_int32()&0xff; }
      }
      ECRYPT_keysetup(&ctxs[i],key,80,iv_len);
      mickey_load_key(&ctx,key);
   }
   for(i=0;i<MICKEY_NR_STREAMS;i++) {
      { 
         int k;
         for(k=0;k<sizeof(iv);k++) { iv[k]=genrand_int32()&0xff; }
      }
      ECRYPT_ivsetup(&ctxs[i],iv);
      mickey_load_IV(&ctx,iv,iv_len);
   }
   mickey_setup(&ctx);

   for(nr=0;nr<nr_iter;nr++) {

      /* Clock and compare outputs */
      mickey_keystream_byte(&ctx,keystream_bytes_bitslice);
      
      for(i=0;i<MICKEY_NR_STREAMS;i++) {
         ECRYPT_keystream_bytes(&ctxs[i],&keystream_byte,1);
         if( keystream_byte != keystream_bytes_bitslice[i] ) {
            fprintf(stderr,"Failed at iteration #=%d, stream #=%d\n",nr,i);
            return -1;
         }
      }
   }

   return 0;
}

static int test_keystream(int nr_iter,int iv_len) {
   int i,nr,out;
   u8 key[10];
   u8 iv[10];
   uword out_bitslice;

   ECRYPT_ctx ctxs[MICKEY_NR_STREAMS];
   mickey_state ctx;

   /* Initialize to all-zeros */
   mickey_init(&ctx);
   for(i=0;i<MICKEY_NR_STREAMS;i++) {
      { 
         int k;
         for(k=0;k<sizeof(key);k++) { key[k]=genrand_int32()&0xff; }
      }
      ECRYPT_keysetup(&ctxs[i],key,80,iv_len);
      mickey_load_key(&ctx,key);
   }
   for(i=0;i<MICKEY_NR_STREAMS;i++) {
      { 
         int k;
         for(k=0;k<sizeof(iv);k++) { iv[k]=genrand_int32()&0xff; }
      }
      ECRYPT_ivsetup(&ctxs[i],iv);
      mickey_load_IV(&ctx,iv,iv_len);
   }
   mickey_setup(&ctx);

   for(nr=0;nr<nr_iter;nr++) {

      /* Clock and compare outputs */
      out_bitslice=mickey_keystream(&ctx);

      for(i=0;i<MICKEY_NR_STREAMS;i++) {
         out=CLOCK_KG(&ctxs[i],0,0);
         if( (out&1) != ((out_bitslice>>i)&1) ) {
            fprintf(stderr,"Failed at iteration #=%d, stream #=%d\n",nr,i);
            return -1;
         }
      }
   }

   return 0;
}

int main(void) {
   int i;
   seed_mt();
   ECRYPT_init();

   for(i=0;i<=80;i++) {
      printf("Test [test_iv] ivlen=%2d...",i);
      if(test_iv(1000,i)) {
         printf("FAILED!\n");
         return -1;
      }
      printf("PASSED!\n");
   }
   for(i=0;i<=80;i++) {
      printf("Test [test_keystream] ivlen=%2d...",i);
      if(test_keystream(1000,i)) {
         printf("FAILED!\n");
         return -1;
      }
      printf("PASSED!\n");
   }
   for(i=0;i<=80;i++) {
      printf("Test [test_keystream_bytes] ivlen=%2d...",i);
      if(test_keystream_bytes(10000,i)) {
         printf("FAILED!\n");
         return -1;
      }
      printf("PASSED!\n");
   }
}
